package algorithms.nchance;

import java.util.*;
import storage.Disk;
import storage.Block;
import configuration.Configuration;
import simulator.Manager;
import simulator.Client;

/**
 * The manager for N-chance algorithm
 * 
 * @author Nodirjon Siddikov
 */
public class NChanceManager extends Manager {
	// global cache information
	// HashMap Key - remote client ID
	// Value - list of remote blocks at that client
	private HashMap<Integer, ArrayList<GlobalCacheBlock>> _gblCache; // collection
																		// of
																		// remote
																		// blocks
																		// grouped
																		// by
																		// client
																		// ID
	private NChanceClient[] _clients;

	// constructor
	public NChanceManager(int id, Disk disk) {
		super(id, disk);
		_gblCache = new HashMap<Integer, ArrayList<GlobalCacheBlock>>();
		_clients = new NChanceClient[Configuration.noOfClients];
	}

	// get the list of clients
	public NChanceClient[] getAllClients() {
		return _clients;
	}

	// set the clients for the manager
	public void setClients(Client[] clients) {
		// get the list of clients
		for (int i = 0; i < clients.length; i++) {
			_clients[i] = (NChanceClient) clients[i];
		}
	}

	// get client reference
	public NChanceClient getClient(int id) {
		for (NChanceClient c : _clients) {
			if (c.getID() == id) {
				return c;
			}
		}
		// returns null if there is no a Client with such ID
		return null;
	}

	// this method is called by a client that had a local cache miss
	public NChanceClient lookUpGlobalCache(int blockID) {
		// serve the disk block from remote client
		for (int hasClientID : _gblCache.keySet()) {
			for (GlobalCacheBlock b : _gblCache.get(hasClientID)) {
				if (b.getBlockID() == blockID) {
					return getClient(hasClientID);
				}
			}
		}
		return null;
	}

	// used when one client asks the manager to relocate the disk block to other
	// client
	public void acceptRelocateRequest(int srcClientID, int destClientID,
			Block block, int reCrclCount) {
		// modified replacement algorithm
		getClient(destClientID)
				.modifiedReplacementAlgorithm(block, reCrclCount);
		// remove the cache block info from OLD client
		removeDiskBlock(srcClientID, block.getID());
		// add the cache block info to NEW client
		addDiskBlock(destClientID, block.getID());
		if (Configuration.detailedStats != 0) {
			System.out.println("Forwarding table is updated: disk block "
					+ block.getID() + " is moved from client " + srcClientID
					+ " to " + destClientID);
		}
	}

	// add the diskBlock to forwarding table
	public void addDiskBlock(int clientID, int diskBlockID) {
		if (_gblCache.get(clientID) == null) {
			// create new list for the src client
			ArrayList<GlobalCacheBlock> list = new ArrayList<GlobalCacheBlock>();
			_gblCache.put(clientID, list);
		}
		_gblCache.get(clientID).add(new GlobalCacheBlock(diskBlockID));
	}

	// remove the diskBlock from forwarding table
	public void removeDiskBlock(int clientID, int diskBlockID) {
		// index of the arraylist element to be removed
		for (GlobalCacheBlock b : _gblCache.get(clientID)) {
			if (b.getBlockID() == diskBlockID) {
				// manager removes the relevant disk block
				_gblCache.get(clientID).remove(b);
				// break the for loop
				break;
			}
		}
	}

	// check if the cache block is a singlet
	public boolean checkForSinglet(int diskBlockID) {
		int count = 0;
		for (int clientID : _gblCache.keySet()) {
			for (GlobalCacheBlock b : _gblCache.get(clientID)) {
				if (b.getBlockID() == diskBlockID) {
					count++;
				}
			}
		}
		// if the number of cache blocks is more than one
		if (count > 1) {
			// cache block is NOT a singlet
			return false;
		} else // if (count == 1) OR (count == 0)
		{
			// it is singlet
			return true;
		}
	}

	public int getGlobalCacheSize() {
		int rmtBlockCount = 0;
		for (int clientID : _gblCache.keySet()) {
			rmtBlockCount += _gblCache.get(clientID).size();
		}
		return rmtBlockCount * Configuration.globalCacheBlockSize;
	}
}